unit CheckLineObserver;

interface

uses
  SysUtils, Classes, DIBImageUtils, Windows, Math, Generics.Collections,
  Dialogs, IniFiles, Forms, CaptureData, RadarMgrUnit;

type

  TCheckQuadrilateral = packed record
    CheckQuadrilateral: TQuadrilateral;
    ExtractQuadrilateral: TQuadrilateral;
  end;

  TCheckedEvent = procedure(ImgBuffer: PAnsichar; CaptureTime: TDateTime;
    ASpeedOutData: TSpeedOutData) of object;

  CheckPassCount = (cpcMin, cpcLow, cpcMiddle, cpcHigh, cpcMax);

  TBufferPool = class
  private
    FPool: TList;
    FUsePool: TList;
    FLock: TRTLCriticalSection;
    procedure Init();
  public
    Max, Min: Integer;
    BufferSize: Integer;
    procedure Put(ABuffer: PAnsichar);
    function Get(): PAnsichar;
    constructor Create(AMin, AMax, ABufferSize: Integer);
    destructor Destroy(); override;
  end;

  TCheckParam = class
  strict private
    FMoveDistance, FPassingDistance: Integer; // unit is m

    FMinPassCount, FLowPassCount, FMiddlePassCount, FHighPassCount,
      FMaxPassCount: Integer;

    FCheckAreaWidth, FExtractAreaDiff: Integer;
    FCheckSpeed: Integer;
    FCheckAreas: TList<TCheckQuadrilateral>;
    FWidth, FHeight: Integer;
    FSwellModel, FErodeModel, FMoveingSwellModel: TOperModel;
    FSavePrevious: Boolean;
    FBufferPool: TBufferPool;
    FThreshold: Integer;
    FPassCount: Integer;
    FMaxCheckLineCount: Integer;

    procedure Init;
  public
    // NegativeCheck: Boolean;
    NotifyEvent: TCheckedEvent;
    property Width: Integer read FWidth;
    property Height: Integer read FHeight;
    property SwellModel: TOperModel read FSwellModel;
    property ErodeModel: TOperModel read FErodeModel;
    property MoveingSwellModel: TOperModel read FMoveingSwellModel;
    property SavePrevious: Boolean read FSavePrevious;
    property BufferPool: TBufferPool read FBufferPool;
    property Threshold: Integer read FThreshold;
    property PassCount: Integer read FPassCount;
    property MaxCheckLineCount: Integer read FMaxCheckLineCount;

    function CalcDifferenceRunTime(SpeedOutData: TSpeedOutData): Integer;
    function CalcPassingRunTime(SpeedOutData: TSpeedOutData): Integer;
    // property MoveDistance: Integer read FMoveDistance;
    // property PassingDistance: Integer read FPassingDistance;

    property CheckAreas: TList<TCheckQuadrilateral>read FCheckAreas;
    procedure SetPassCount(const PassCount: CheckPassCount);
    procedure AddCheckLine(const ABeginPoint, AEndPoint: TPoint); overload;
    procedure AddCheckLine(const ALine: TLine); overload;
    procedure ClearCheckLines();
    constructor Create(AWidth, AHeight: Integer);
    destructor Destroy(); override;
  end;

  TCheckLineObserverThread = class(TThread)
  private
    PreviousSnapBuffer, CurSnapBuffer: PAnsichar;
    FParam: TCheckParam;
    CheckTime: TDateTime;
    Interval: Integer;
    SpeedOutData: TSpeedOutData;

    procedure Notify();
  protected
    procedure Execute; override;
  public
    constructor Create(const Param: TCheckParam; ASpeedOutData: TSpeedOutData;
      PreviousSnap, CurSnap: PAnsichar; AInterval: Integer); overload;
    destructor Destroy(); override;
  end;

implementation

uses TLoggerUnit;

const
  MAX_DIFFERENCE_RUNTIME = 500;
  MAX_PASS_RUNTIME = 1000;
  { TCheckLineObserver }

constructor TCheckLineObserverThread.Create(const Param: TCheckParam;
  ASpeedOutData: TSpeedOutData; PreviousSnap, CurSnap: PAnsichar;
  AInterval: Integer);
begin

  CheckTime := Now();
  SpeedOutData := ASpeedOutData;
  Interval := AInterval;
  FParam := Param;
  PreviousSnapBuffer := FParam.BufferPool.Get();
  CopyMemory(PreviousSnapBuffer, PreviousSnap, FParam.BufferPool.BufferSize);
  CurSnapBuffer := FParam.BufferPool.Get();
  CopyMemory(CurSnapBuffer, CurSnap, FParam.BufferPool.BufferSize);
  FreeOnTerminate := True;
  inherited Create(False);
end;

destructor TCheckLineObserverThread.Destroy;
begin
  FParam.BufferPool.Put(PreviousSnapBuffer);
  FParam.BufferPool.Put(CurSnapBuffer);
  inherited;
end;

procedure TCheckLineObserverThread.Execute;
var
  mCount: Integer;
  CheckArea: TCheckQuadrilateral;
  SnapDestOperBuffer: PAnsichar;
begin
  FreeOnTerminate := True;

  SnapDestOperBuffer := FParam.BufferPool.Get();
  try
    for CheckArea in FParam.CheckAreas do
    begin
      if Terminated then
        Exit;
      CopyMemory(SnapDestOperBuffer, CurSnapBuffer,
        FParam.BufferPool.BufferSize);
      DiffValue(PreviousSnapBuffer, SnapDestOperBuffer, FParam.Width,
        FParam.Height, CheckArea.CheckQuadrilateral, FParam.Threshold);
      if Terminated then
        Exit;

      if SpeedOutData.Mode <> cmMoveing then
      begin
        mCount := DenoisingTotal(SnapDestOperBuffer, FParam.Width,
          FParam.Height, FParam.SwellModel, CheckArea.ExtractQuadrilateral,
          AnsiChar(255));
      end
      else
        mCount := DenoisingTotal(SnapDestOperBuffer, FParam.Width,
          FParam.Height, FParam.MoveingSwellModel,
          CheckArea.ExtractQuadrilateral, AnsiChar(255));

      // Swell(SnapDestOperBuffer, SnapOperBuffer, FParam.Width, FParam.Height,
      // FParam.SwellModel, CheckArea.CheckQuadrilateral);
      // if Terminated then
      // Exit;
      // Erode(SnapOperBuffer, SnapDestOperBuffer, FParam.Width, FParam.Height,
      // FParam.ErodeModel, CheckArea.CheckQuadrilateral);
      // if Terminated then
      // Exit;
      // mCount := TotalValueCount(SnapDestOperBuffer, FParam.Width, FParam.Height,
      // CheckArea.ExtractQuadrilateral, AnsiChar(255));
      if Terminated then
        Exit;
      if mCount >= FParam.PassCount then
      begin
        // debug
        // CopyMemory(PreviousSnapBuffer, SnapDestOperBuffer,
        // FParam.BufferPool.BufferSize);
        Synchronize(Notify);
        Exit;
      end;
    end;
  finally
    FParam.BufferPool.Put(SnapDestOperBuffer);
  end;
end;

procedure TCheckLineObserverThread.Notify;
begin
  if Assigned(FParam.NotifyEvent) then
    if FParam.SavePrevious then
      FParam.NotifyEvent(PreviousSnapBuffer, CheckTime, SpeedOutData)
    else
      FParam.NotifyEvent(CurSnapBuffer, CheckTime, SpeedOutData)
end;

{ TCheckParam }

procedure TCheckParam.AddCheckLine(const ALine: TLine);
begin
  AddCheckLine(ALine.BeginPoint, ALine.EndPoint);
end;

function TCheckParam.CalcDifferenceRunTime(SpeedOutData: TSpeedOutData)
  : Integer;
begin
  if SpeedOutData.Mode = cmMoveing then
  begin
    if SpeedOutData.Zone = czSame then
    begin
      Result := Result -
        Round(FMoveDistance * ((60 * 60 * 1000) /
        ((SpeedOutData.TargetSpeed - SpeedOutData.PatrolSpeed) * 1000)))
    end
    else
    begin
      // maybe SpeedOutData.TargetSpeed + SpeedOutData.PatrolSpeed
      Result := Round(FMoveDistance * ((60 * 60 * 1000) /
        ((SpeedOutData.TargetSpeed) * 1000)));
    end;
  end
  else
    Result := Round(FMoveDistance * ((60 * 60 * 1000) /
      (SpeedOutData.TargetSpeed * 1000)));
  if Result < 20 then
    Result := 20;

  Result := Min(MAX_DIFFERENCE_RUNTIME, Result);
end;

function TCheckParam.CalcPassingRunTime(SpeedOutData: TSpeedOutData): Integer;
begin
  if SpeedOutData.Mode = cmMoveing then
  begin
    if SpeedOutData.Zone = czSame then
    begin
      Result := Result - Round(FPassingDistance *
        ((60 * 60 * 1000) / ((SpeedOutData.TargetSpeed -
        SpeedOutData.PatrolSpeed) * 1000)))
    end
    else
    begin
      Result := Round(FPassingDistance *
        ((60 * 60 * 1000) / ((SpeedOutData.TargetSpeed +
        SpeedOutData.PatrolSpeed) * 1000)));
    end;
  end
  else
    Result := Round(FPassingDistance * ((60 * 60 * 1000) /
      (SpeedOutData.TargetSpeed * 1000)));
  if Result < 50 then
    Result := 50;
  Result := Min(MAX_PASS_RUNTIME, Result);
end;

procedure TCheckParam.ClearCheckLines;
begin
  FCheckAreas.Clear;
end;

constructor TCheckParam.Create(AWidth, AHeight: Integer);
begin
  FCheckAreas := TList<TCheckQuadrilateral>.Create();
  FWidth := AWidth;
  FHeight := AHeight;
  Init();

end;

destructor TCheckParam.Destroy;
begin
  if FBufferPool <> nil then
    FBufferPool.Free;
  if FCheckAreas <> nil then
    FCheckAreas.Free;
  inherited;
end;

procedure TCheckParam.Init;
var
  ParamSetting: TIniFile;
begin
  ParamSetting := TIniFile.Create(ExtractFilePath(Application.ExeName) +
    'CheckParam.ini');
  try
    FSavePrevious := ParamSetting.ReadBool('CheckParam', 'SavePrevious', True);

    FMoveDistance := ParamSetting.ReadInteger('CheckParam', 'MoveDistance', 3);
    FPassingDistance := ParamSetting.ReadInteger('CheckParam',
      'PassingDistance', 20);

    FCheckAreaWidth := ParamSetting.ReadInteger('CheckParam',
      'CheckAreaWidth', 50);
    FExtractAreaDiff := ParamSetting.ReadInteger('CheckParam',
      'ExtractAreaDiff', 4);

    FThreshold := ParamSetting.ReadInteger('CheckParam', 'Threshold', 40);
    FSwellModel.Templete :=
      DIBOperTemplete(ParamSetting.ReadInteger('CheckParam', 'SwellModel',
      Integer(doRect)));
    FSwellModel.Size := ParamSetting.ReadInteger('CheckParam', 'SwellSize', 4);
    FErodeModel.Templete :=
      DIBOperTemplete(ParamSetting.ReadInteger('CheckParam', 'ErodeModel',
      Integer(doRect)));
    FErodeModel.Size := ParamSetting.ReadInteger('CheckParam', 'ErodeSize', 4);

    FMoveingSwellModel.Size := ParamSetting.ReadInteger('CheckParam',
      'MoveingSwellSize', 25);
    FMoveingSwellModel.Templete :=
      DIBOperTemplete(ParamSetting.ReadInteger('CheckParam',
      'MoveingSwellModel', Integer(doRect)));

    FMinPassCount := ParamSetting.ReadInteger('CheckParam',
      'PassCountSupperLow', 9000);
    FLowPassCount := ParamSetting.ReadInteger('CheckParam',
      'PassCountLow ', 7000);
    FMiddlePassCount := ParamSetting.ReadInteger('CheckParam',
      'PassCountMiddle ', 5000);
    FHighPassCount := ParamSetting.ReadInteger('CheckParam',
      'PassCountHigh  ', 3000);
    FMaxPassCount := ParamSetting.ReadInteger('CheckParam',
      'PassCountSupperHigh', 1000);

    FBufferPool := TBufferPool.Create(ParamSetting.ReadInteger('CheckParam',
      'BufferPoolMin', 100), ParamSetting.ReadInteger('CheckParam',
      'BufferPoolMax', 200), FWidth * FHeight * 3);

    FMaxCheckLineCount := ParamSetting.ReadInteger('CheckParam',
      'MaxChcekLineCount', 2);
  finally
    ParamSetting.Free;
  end;
end;

procedure TCheckParam.SetPassCount(const PassCount: CheckPassCount);
begin
  case PassCount of
    cpcMin:
      FPassCount := FMinPassCount;
    cpcLow:
      FPassCount := FLowPassCount;
    cpcMiddle:
      FPassCount := FMiddlePassCount;
    cpcHigh:
      FPassCount := FHighPassCount;
    cpcMax:
      FPassCount := FMaxPassCount;
  end;
end;

procedure TCheckParam.AddCheckLine(const ABeginPoint, AEndPoint: TPoint);
var
  CheckBeginPoint, CheckEndPoint: TPoint;

  CheckArea: TCheckQuadrilateral;

  procedure CheckPoint(var APoint: TPoint);
  begin
    if APoint.X < FCheckAreaWidth then
      APoint.X := FCheckAreaWidth;
    if APoint.X > (FWidth - FCheckAreaWidth - 1) then
      APoint.X := FWidth - FCheckAreaWidth - 1;
    if APoint.Y < FCheckAreaWidth then
      APoint.Y := FCheckAreaWidth;
    if APoint.Y > (FHeight - FCheckAreaWidth - 1) then
      APoint.Y := FHeight - FCheckAreaWidth - 1;
  end;

begin
  CheckBeginPoint := ABeginPoint;
  CheckEndPoint := AEndPoint;
  CheckPoint(CheckEndPoint);
  CheckPoint(CheckBeginPoint);
  if Abs(CheckEndPoint.Y - CheckBeginPoint.Y) >= FCheckAreaWidth * 2 then
  begin
    if CheckEndPoint.Y > CheckBeginPoint.Y then
    begin
      CheckArea.CheckQuadrilateral.LeftTop.X :=
        Max(CheckBeginPoint.X - FCheckAreaWidth, 0);
      CheckArea.CheckQuadrilateral.LeftTop.Y :=
        Min(CheckBeginPoint.Y + FCheckAreaWidth, FWidth - 1);
      CheckArea.CheckQuadrilateral.RightTop.X :=
        Min(CheckBeginPoint.X + FCheckAreaWidth, FWidth - 1);
      CheckArea.CheckQuadrilateral.RightTop.Y :=
        Max(CheckBeginPoint.Y - FCheckAreaWidth, 0);

      CheckArea.CheckQuadrilateral.LeftBottom.X :=
        Max(CheckEndPoint.X - FCheckAreaWidth, 0);
      CheckArea.CheckQuadrilateral.LeftBottom.Y :=
        Min(CheckEndPoint.Y + FCheckAreaWidth, FWidth - 1);
      CheckArea.CheckQuadrilateral.RightBottom.X :=
        Min(CheckEndPoint.X + FCheckAreaWidth, FWidth - 1);
      CheckArea.CheckQuadrilateral.RightBottom.Y :=
        Max(CheckEndPoint.Y - FCheckAreaWidth, 0);
    end
    else
    begin
      CheckArea.CheckQuadrilateral.LeftTop.X :=
        Max(CheckEndPoint.X - FCheckAreaWidth, 0);
      CheckArea.CheckQuadrilateral.LeftTop.Y :=
        Max(CheckEndPoint.Y - FCheckAreaWidth, 0);
      CheckArea.CheckQuadrilateral.RightTop.X :=
        Min(CheckEndPoint.X + FCheckAreaWidth, FWidth - 1);
      CheckArea.CheckQuadrilateral.RightTop.Y :=
        Min(CheckEndPoint.Y + FCheckAreaWidth, FWidth - 1);

      CheckArea.CheckQuadrilateral.LeftBottom.X :=
        Max(CheckBeginPoint.X - FCheckAreaWidth, 0);
      CheckArea.CheckQuadrilateral.LeftBottom.Y :=
        Max(CheckBeginPoint.Y - FCheckAreaWidth, 0);
      CheckArea.CheckQuadrilateral.RightBottom.X :=
        Min(CheckBeginPoint.X + FCheckAreaWidth, FWidth - 1);
      CheckArea.CheckQuadrilateral.RightBottom.Y :=
        Min(CheckBeginPoint.Y + FCheckAreaWidth, FWidth - 1);
    end;
  end
  else
  begin
    if CheckEndPoint.X > CheckBeginPoint.X then
    begin
      CheckArea.CheckQuadrilateral.LeftTop.X := CheckBeginPoint.X;
      CheckArea.CheckQuadrilateral.LeftTop.Y :=
        Max(CheckBeginPoint.Y - FCheckAreaWidth, 0);
      CheckArea.CheckQuadrilateral.LeftBottom.X := CheckBeginPoint.X;
      CheckArea.CheckQuadrilateral.LeftBottom.Y :=
        Min(CheckBeginPoint.Y + FCheckAreaWidth, FHeight - 1);

      CheckArea.CheckQuadrilateral.RightTop.X := CheckEndPoint.X;
      CheckArea.CheckQuadrilateral.RightTop.Y :=
        Max(CheckEndPoint.Y - FCheckAreaWidth, 0);
      CheckArea.CheckQuadrilateral.RightBottom.X := CheckEndPoint.X;
      CheckArea.CheckQuadrilateral.RightBottom.Y :=
        Min(CheckEndPoint.Y + FCheckAreaWidth, FHeight - 1);
    end
    else
    begin
      CheckArea.CheckQuadrilateral.LeftTop.X := CheckEndPoint.X;
      CheckArea.CheckQuadrilateral.LeftTop.Y :=
        Max(CheckEndPoint.Y - FCheckAreaWidth, 0);
      CheckArea.CheckQuadrilateral.LeftBottom.X := CheckEndPoint.X;
      CheckArea.CheckQuadrilateral.LeftBottom.Y :=
        Min(CheckEndPoint.Y + FCheckAreaWidth, FHeight - 1);

      CheckArea.CheckQuadrilateral.RightTop.X := CheckBeginPoint.X;
      CheckArea.CheckQuadrilateral.RightTop.Y :=
        Max(CheckBeginPoint.Y - FCheckAreaWidth, 0);
      CheckArea.CheckQuadrilateral.RightBottom.X := CheckBeginPoint.X;
      CheckArea.CheckQuadrilateral.RightBottom.Y :=
        Min(CheckBeginPoint.Y + FCheckAreaWidth, FHeight - 1);
    end;
  end;

  CheckArea.ExtractQuadrilateral.LeftTop.X :=
    Min(CheckArea.CheckQuadrilateral.LeftTop.X + FExtractAreaDiff, FWidth - 1);
  CheckArea.ExtractQuadrilateral.LeftTop.Y :=
    Min(CheckArea.CheckQuadrilateral.LeftTop.Y + FExtractAreaDiff, FHeight - 1);
  CheckArea.ExtractQuadrilateral.RightTop.X :=
    Max(CheckArea.CheckQuadrilateral.RightTop.X - FExtractAreaDiff, 0);
  CheckArea.ExtractQuadrilateral.RightTop.Y :=
    Min(CheckArea.CheckQuadrilateral.RightTop.Y + FExtractAreaDiff,
    FHeight - 1);
  CheckArea.ExtractQuadrilateral.LeftBottom.X :=
    Min(CheckArea.CheckQuadrilateral.LeftBottom.X + FExtractAreaDiff,
    FWidth - 1);
  CheckArea.ExtractQuadrilateral.LeftBottom.Y :=
    Max(CheckArea.CheckQuadrilateral.LeftBottom.Y - FExtractAreaDiff, 0);
  CheckArea.ExtractQuadrilateral.RightBottom.X :=
    Max(CheckArea.CheckQuadrilateral.RightBottom.X - FExtractAreaDiff, 0);
  CheckArea.ExtractQuadrilateral.RightBottom.Y :=
    Max(CheckArea.CheckQuadrilateral.RightBottom.Y - FExtractAreaDiff, 0);

  FCheckAreas.Add(CheckArea);
end;

{ TBufferPool }

constructor TBufferPool.Create(AMin, AMax, ABufferSize: Integer);
begin
  inherited Create();
  Min := AMin;
  Max := AMax;
  BufferSize := ABufferSize;
  Init;
end;

destructor TBufferPool.Destroy;
var
  i: Integer;
begin
  EnterCriticalSection(FLock);
  try
    for i := 0 to FPool.Count - 1 do
      FreeMem(FPool[i], BufferSize);
    FreeAndNil(FPool);
    for i := 0 to FUsePool.Count - 1 do
      FreeMem(FUsePool[i], BufferSize);
    FreeAndNil(FUsePool);
  finally
    LeaveCriticalSection(FLock);
  end;

  DeleteCriticalSection(FLock);
  inherited;
end;

function TBufferPool.Get: PAnsichar;
begin
  EnterCriticalSection(FLock);
  try
    if FPool.Count <= 0 then
    begin
      if FUsePool.Count >= Max then
        raise Exception.Create('Pool is Max');
      Result := AllocMem(BufferSize);
    end
    else
    begin
      Result := FPool[0];
      FPool.Remove(Result);
    end;
    FUsePool.Add(Result);
  finally
    LeaveCriticalSection(FLock);
  end;
end;

procedure TBufferPool.Init;
var
  i: Integer;
begin
  InitializeCriticalSection(FLock);
  FPool := TList.Create;
  FUsePool := TList.Create;
  for i := 0 to Min - 1 do
  begin
    FPool.Add(AllocMem(BufferSize));
  end;
end;

procedure TBufferPool.Put(ABuffer: PAnsichar);
begin
  EnterCriticalSection(FLock);
  try
    if FUsePool.Remove(ABuffer) >= 0 then
    begin
      if (FUsePool.Count + FPool.Count) >= Min then
      begin
        FreeMem(ABuffer, BufferSize);
      end
      else if FPool.IndexOf(ABuffer) < 0 then
        FPool.Add(ABuffer)
      else
        raise Exception.Create('Buffer in Pool');
    end
    else
      raise Exception.Create('Not Pool Buffer');
  finally
    LeaveCriticalSection(FLock);
  end;
end;

end.
